package hwip.processing;

import java.net.Socket;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import de.hmm.hwip.configuration.api.ISystemDeviceConf;
import de.hmm.hwip.connection.api.IConOut;
import de.hmm.hwip.general.IAndroidContext;
import de.hmm.hwip.general.IMessageContent;
import de.hmm.hwip.messagehandler.api.IipcToAndroid;
import de.hmm.hwip.storage.api.ISQLite;

/**
 * System Device Service
 * 
 * Receiver and Sender of Sensor and Actor data
 * manages sensor data, send them to gateway or spot where the connected actor is  
 * 
 * @author johannes mendel
 * @version 17.10.2012
 *
 */
public class Sys_Device implements IMessageContent {

	/**
	 * Class TAG
	 */
	private final String TAG = "Sys_Device Message";
	
	/**
	 * security flag
	 */
	boolean securityFlag;
	
	/**
	 * variable needed for service tracker 
	 */
	private BundleContext context;
	
	/**
	 * sys_device config service tracker
	 */
	private ServiceTracker serviceTrackerConf;
	
	/**
	 * database service tracker
	 */
	private ServiceTracker serviceTrackerDB;
	
	/**
	 * OutConnection service tracker
	 */
	private ServiceTracker serviceTrackerOutCon;
	
	/**
	 * Android Context service tracker
	 */
	private ServiceTracker contextServiceTracker;
	
	/**
	 * serivce for configuration, got by service tracker
	 */
	private ISystemDeviceConf serviceConf;
	
	/**
	 * serivce for datasource, got by service tracker
	 */
	private ISQLite serviceDB;
	
	/**
	 * service for outgoing connection to answer receiver
	 */
	private IConOut serviceOutCon;
	
	/**
	 * service for Android App Context
	 */
	private IAndroidContext androidContextService;
	
	/** OSGi Service Tracker for Android IPC Service **/
	ServiceTracker serviceTrackerIpc;
	
	/**
	 * OSGi-Service Interface Iipc
	 */
	IipcToAndroid androidIpcService;
	
	/**
	 * query for this service
	 */
	private String query;
	
	/**
	 * result of the query
	 */
	private Cursor queryResult;
	
	/**
	 * result of insert query
	 */
	@SuppressWarnings("unused")
	private int insertOk = 0;
	
	/** Android App Context **/
	private Context appContext;

	/**
	 * type of query 
	 * 1 - select
	 * 2 - insert/Update/delete
	 * 3 - multiinsert
	 * 4 - get complete db copy
	 * 9 - no query
	 */
	private int queryType = 0;
	
	/**
	 * Identifier of Actions
	 * 0 - incoming data -> start of processing
	 * 1 - ask if sys_device is in device table
	 * 2 - tabe is in devices -> add to sys_device
	 * 3 - send new System Device to Gateway
	 * 4 - spot_id is not the same as in db
	 * 5 - read connected_device if there is a script and send to receiver
	 * 6 - got command data -> send to hardware
	 * 7 - sends updated spotId from System device data to all other spots
	 * 8 - sends new connected system device to all spots
	 * 9 - do nothing
	 */
	private int intraaction = 0;
	
	/**
	 * calling socket connection
	 */
	private String receiver;
	
	/**
	 * member to store error from data source connection
	 */
	private String errorDB = "";
	
	/**
	 * Jsonobject of sended data by client
	 */
	private JSONObject payload;
	
	/**
	 * waiting for database feedback
	 */
	private boolean wait = true;
	
	/** Map to store payload **/
	private Map<String, Object> data;
	
	/**Id of used Spot*/
	private int spotId;
	
	/** Sys_dev_id */
	private int sysDevId;
	
	/**identifier if Spot = gateway **/
	private boolean isGate = false;
	
	@Override
	public void run() {
		// getting the configuration-unit-service
		serviceTrackerConf = new ServiceTracker(this.context, ISystemDeviceConf.class.getName(), new Customizer() );
		serviceTrackerConf.open();
		
		// getting the datasource service
		serviceTrackerDB = new ServiceTracker(this.context, ISQLite.class.getName(), null );
		serviceTrackerDB.open();
		serviceDB = (ISQLite) serviceTrackerDB.getService();
		
		// getting the android context service
		contextServiceTracker = new ServiceTracker(this.context, IAndroidContext.class.getName(), null );
		contextServiceTracker.open();
		androidContextService = (IAndroidContext) contextServiceTracker.getService();

		//getting the IPC service to communicate with the hardware
		serviceTrackerIpc = new ServiceTracker(context, IipcToAndroid.class.getName(), null);
		serviceTrackerIpc.open();
		androidIpcService = (IipcToAndroid) serviceTrackerIpc.getService();
		
		if(androidContextService != null){
			appContext = androidContextService.transferContext();
			
			//0 = private mode
			SharedPreferences settings = appContext.getSharedPreferences("hwip", 0);
			spotId = settings.getInt("spotId", 1);
			receiver = settings.getString("gatewayip", null);
			isGate = settings.getBoolean("isgateway", false);
			
		}
		
		if(serviceConf != null){
			// getting payload
			data = new LinkedHashMap<String, Object>();
			
			try {
				data = JsonToMap(payload);
			} catch (JSONException e) {
				Log.e(TAG, "Json Exception: " + e.getMessage());
			}
			
			performDbRequest();
			showDatainGui();
		}
	}

	private void performDbRequest(){
		if(serviceDB != null){
			String command = (String) data.get("command");
			if((command != null) && (intraaction == 0)){
				if(command.equals("setnewsysdev")){
					Log.i(TAG, "Write new system device to DB");
					queryType = 2;
					int sysdevID = (Integer)data.get("sys_dev_id");
					int spotID = (Integer) data.get("spot_id_fk");
					int devIdFk = (Integer) data.get("device_id_fk");
					String sn_addr = (String) data.get("sn_addr");
					String specification = (String) data.get("sys_specific");
					String[] attribute = new String[data.size()-7];
					int i = 0;
					for(String a : data.keySet()){
						if(!(a.equals("sn_addr") || a.equals("sys_specific") || a.equals("sys_dev_id") || a.equals("spot_id_fk") 
								|| a.equals("device_id_fk") || a.equals("command") || a.equals("service"))){
							attribute[i] = (String) data.get(a);
							i++;
						}
					}
					query = serviceConf.getQueryForSetSysDevice(sysdevID, devIdFk, spotID, sn_addr, specification, attribute);
					if(isGate)
						intraaction = 8;
					else
						intraaction = 9;
				}else if(command.equals("updatesysdevdata")){
					Log.i(TAG, "Update system device in DB");
					queryType = 2;
					String[] attribute = new String[data.size()-3];
					int i = 0;
					for(String a : data.keySet()){
						if(!(a.equals("sys_dev_id") || a.equals("service") || a.equals("command"))){
							attribute[i] = (String) data.get(a);
							i++;
						}
					}
					query = serviceConf.getQueryForUpdateSysDevice((Integer)data.get("sys_dev_id"), attribute);
					intraaction = 9;
				}else if(command.equals("updatesysdevspot")){
					Log.i(TAG, "Update system device - Spot mapping in DB");
					queryType = 2;
					query = serviceConf.getQueryForUpdateSpotSysDevice((Integer) data.get("sys_dev_id"), (Integer) data.get("spot_id_fk"));
					if(isGate)
						intraaction = 7;
					else
						intraaction = 9;
				}else if(command.equals("commandscript")){
					Log.i(TAG, "Get Device by DeviceId from DB");
					queryType = 1;
					query = serviceConf.getQueryForDeviceByDeviceId((Integer) data.get("device_id_fk"));
					intraaction = 6;
				}
			}else{
				switch(intraaction){
				case 0: 
					Log.i(TAG, "Incoming Data from System Device - check if there is a reference in DB");
					queryType = 1;
					query = serviceConf.getQueryForSysDeviceBySN((Integer) data.get("radio"), (String) data.get("sn_addr"));
					break;
				
				case 1: 
					Log.i(TAG, "Check if the Hardware Device is in generel stored in DB");
					queryType = 1;
					query = serviceConf.getQueryForDeviceId((Integer)(data.get("radio")), (String) data.get("model_type"));
					break;
				
				case 2: 
					Log.i(TAG, "Set System Device to Database");

					queryResult.moveToFirst();
					int deviceID = queryResult.getInt(0);
					String sn_addr = (String) data.get("sn_addr");
					String specification = (String) data.get("d_specific");
					String[] attribute = new String[data.size()-5];
					int i = 0;
					for(String a : data.keySet()){
						if(!(a.equals("sn_addr") || a.equals("d_specific") || a.equals("model_type") || a.equals("radio") || a.equals("service"))){
							attribute[i] = (String) data.get(a);
							i++;
						}
					}
					queryType = 2;
					query = serviceConf.getQueryForSetSysDevice(spotId, deviceID, sn_addr, specification, attribute);
					queryResult = null;
					break;
				
				case 3: 
					Log.i(TAG, "Get system device from DB to send it to the gateway");
					queryType = 1;
					query = serviceConf.getQueryForSysDeviceBySN((Integer) data.get("radio"), (String) data.get("sn_addr"));
					break;
				
				case 4: 
					Log.i(TAG, "Update system device - spot mapping in DB");
					queryType = 2;
					query = serviceConf.getQueryForUpdateSpotSysDevice(queryResult.getInt(0), spotId);
					break;
					
				case 5: 
					Log.i(TAG, "Get connected system devices from DB");
					queryType = 1;
					query = serviceConf.getQueryForConDevLookUp(sysDevId);
				
				case 7:
					Log.i(TAG, "Get all Spot IPs from DB");
					queryType = 1;
					query = serviceConf.getQueryForSpotIps();
				
				case 8:
					Log.i(TAG, "Get all Spot IPs from DB");
					queryType = 1;
					query = serviceConf.getQueryForSpotIps();	
				}
				
			}
			serviceDB.transferData(this);
			/*
			 * Synchronizing for the purpose that this thread can wait until
			 * the DB thread is finished. If this is the case the DB thread
			 * calls the synchronized <code>stopWaiting()</code> method and so
			 * becomes the monitor of this thread. The thread will get notified
			 * and stops waiting.
			 */
			synchronized (this) 
			{
				while(wait)
				{
					try 
					{
						Log.i(TAG, "Waiting for Db to finish");
						this.wait();
					} 
					catch (InterruptedException e) 
					{
						errorDB += "Could not execute request!";
						Log.e(TAG, "Syncerror: " + e.getMessage());
					}
				}
			}
			
			Log.i(TAG, "Data received from DB ");
			// generate answer to receiver
			answerReceiver();
		}
	}
	
	@Override
	public void answerReceiver() {
		// getting the configuration-unit-service
		serviceTrackerOutCon = new ServiceTracker(this.context, IConOut.class.getName(), null );
		serviceTrackerOutCon.open();
		serviceOutCon = (IConOut) serviceTrackerOutCon.getService();
		
		JSONObject answer = generateAnswer();
		//queryResult.close();
		if(intraaction == 6){
			answerHardware();
		}else if(answer != null && serviceOutCon != null){
			Log.i(TAG, "Send System Device Data to: " + receiver);
			serviceOutCon.startCon(receiver, this.context, appContext, answer, true);
			if(intraaction == 5)
				performDbRequest();
		}
	}

	@Override
	public JSONObject generateAnswer() {
		JSONObject answer = new JSONObject();
		String column = "";
		boolean reperformrequest = false;
		try {
			answer.put("service", payload.get("service"));
			switch(intraaction){
			case 0: 
				if(queryResult.getCount() == 0){
					intraaction = 1;
					reperformrequest = true;
					break;
				}else{
					queryResult.moveToFirst();
					sysDevId = queryResult.getInt(0);
					if(queryResult.getInt(1) == spotId){
						Log.i(TAG, "Spot is same as in DB ... send Data to Gateway");
						
						answer.put("command", "updatesysdevdata");
						
						queryResult.moveToFirst();
						answer.put("sys_dev_id", sysDevId);
						
						for(String a : data.keySet()){
							if(!(a.equals("sn_addr") || a.equals("d_specific") || a.equals("model_type") || a.equals("radio"))){
								answer.put(a, (String) data.get(a));
							}
						}						
						
					intraaction = 5;
					break;
					}else{
						intraaction = 4;
						reperformrequest = true;
						break;

					}
				}
			case 1: 
				System.out.println("intra: " + intraaction + " qr: " + queryResult.getCount());
				if(queryResult.getCount() == 0){
					return null;
				}else{
					intraaction = 2;
					reperformrequest = true;
					break;
				}
				
			case 2: intraaction = 3;
					reperformrequest = true;
					break;
					
			case 3:

				answer.put("command", "setnewsysdev");
				if(queryResult.moveToFirst()){

					for(int i = 0; i < queryResult.getColumnCount(); i++){
						column = queryResult.getColumnName(i);
						if(column.equals("sys_dev_id") || column.equals("device_id_fk") || column.equals("spot_id_fk")){
							answer.put(column, queryResult.getInt(i));
						}else{
							answer.put(column, queryResult.getString(i));
						}
					}
				}else
					Log.w(TAG, "no Entry for that system device in DB.");
				break;
				
			case 4: 

				answer.put("command", "updatesysdevspot");
				answer.put("sys_dev_id", sysDevId);
				answer.put("spot_id_fk", spotId);
					
				intraaction = 5;
				break;
				
			case 5:
				if(queryResult.getCount()== 0){
					return null;
				}else{
					//check if there are commands in Table matching to device
					//s.ip_address, c.value_1, com.attr, c.command_id_fk,sys.sys_dev_id, sys.device_id_fk, sys.sn_addr, c.cs_id, c.command_id_fk
					while(queryResult.moveToNext()){
						answer = new JSONObject();
						if(data.containsKey(queryResult.getString(2))){ //&& data.get(queryResult.getString(2)).equals(queryResult.getString(1))){
							receiver = queryResult.getString(0);
							
							answer.put("service", "Sys_Device");
							answer.put("command", "commandscript");

							for(int i = 1; i < queryResult.getColumnCount(); i++){
								column = queryResult.getColumnName(i);
								if(column.equals("sn_addr") || column.equals("value") || column.equals("attr")){
									answer.put(column, queryResult.getString(i));
								}else{
									answer.put(column, queryResult.getInt(i));
								}
							}
							answer.put("value", data.get(queryResult.getString(2)));
							serviceOutCon.startCon(receiver, this.context, appContext, answer, true);
						}	
					}
					return null;
				}
			case 7:	
				while(queryResult.moveToNext()){
					answer = new JSONObject();

					answer.put("command", "updatesysdevspot");
					answer.put("sys_dev_id", data.get("sys_dev_id")); 
					answer.put("spot_id_fk", data.get("spot_id_fk"));
					receiver = queryResult.getString(0);

					serviceOutCon.startCon(receiver, this.context, this.appContext, answer, true);
				}
				return null;
				
			case 8: 	
				int sysdevID = (Integer)data.get("sys_dev_id");
				int spotID = (Integer) data.get("spot_id_fk");
				int devIdFk = (Integer) data.get("device_id_fk");
				String sn_addr = (String) data.get("sn_addr");
				String specification = (String) data.get("sys_specific");
				if(queryResult != null){		
					queryResult.moveToFirst();
					while(queryResult.moveToNext()){
						answer = new JSONObject();
						answer.put("command", "setnewsysdev");
						answer.put("sys_dev_id", sysdevID); 
						answer.put("spot_id_fk", spotID);
						answer.put("device_id_fk", devIdFk);
						answer.put("sn_addr", sn_addr);
						answer.put("sys_specific", specification);
						
						receiver = queryResult.getString(0);
					
						serviceOutCon.startCon(receiver, this.context, this.appContext, answer, true);
					}
				}else
					performDbRequest();
				
				return null;
				
			case 9: return null;	
			
			}
		} catch (JSONException e) {
			Log.e(TAG, "Json Exception: " + e.getMessage() + " -> " +e.getLocalizedMessage());
		}
		
		if(reperformrequest){
			wait = true;
			performDbRequest();
			return null;
		}
		if(errorDB != null || errorDB != ""){
			Log.w(TAG, "Database failure occurred: " + errorDB);
		}
		
		return answer;
	}
	
	/** sends message to hardware connector **/
	private void answerHardware(){
		if(androidIpcService != null){
			Bundle message = new Bundle();
			//s.ip_address, c.value_1, com.attr, c.command_id_fk,sys.sys_dev_id, sys.device_id_fk, sys.sn_addr, c.cs_id, c.command_id_fk
			message.putString("sn_addr", (String) data.get("sn_addr"));
			message.putString("model_type", (String)data.get("model_type"));
			message.putString("attr", (String)data.get("attr"));
			message.putString("value", (String)data.get("value"));
			androidIpcService.ipcMessage(queryResult.getInt(1), message);
		}
	}
	
	/** transform a JsonObject to a Map **/
	@SuppressWarnings("rawtypes")
	private static Map<String, Object> JsonToMap(JSONObject object) throws JSONException {
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		Iterator keys = object.keys();
		while (keys.hasNext()) {
			String key = (String) keys.next();
			map.put(key, object.get(key));
		}
		return map;
	}
	
	@Override
	public void setBundleContext(BundleContext context) {
		this.context = context;

	}

	@Override
	public void setConnection(Socket connection) {
		//this.receiver = connection;

	}

	@Override
	public void setPayload(JSONObject payload) {
		this.payload = payload;

	}

	@Override
	public void setSecurityFlag(boolean flag) {
		this.securityFlag = flag;

	}

	@Override
	public synchronized void stopWaiting() {
		this.wait = false;
		this.notify();
	}

	@Override
	public Map<String, Object> getCompleteDB() {
		// not used
		return null;
	}

	@Override
	public String getQuery() {
		return this.query;
	}

	@Override
	public int getQueryType() {
		return this.queryType;
	}

	@Override
	public void setErrorResult(String error) {
		this.errorDB = error;
		
	}

	@Override
	public void setQueryResult(Cursor result) {
		queryResult = result;
		
	}

	@Override
	public void setQueryResult(int result) {
		this.insertOk = result;		
	}
	
	@Override
	public void setCompleteDbResult(JSONObject db) {
		//not used
	}
	
	/** Servicetracker for Storage Service **
	 * if dbService is coming online tracker will notice and send Applicationcontext */

	private class Customizer implements ServiceTrackerCustomizer{

		@Override
		public Object addingService(final ServiceReference reference) {
			Log.i(TAG, "---------> DB Service is connected <---------");
			serviceConf = (ISystemDeviceConf) context.getService(reference);

			return serviceConf;
		}
		
		@Override
		public void modifiedService(ServiceReference arg0, Object arg1) {
			// nothing to do
			
		}

		@Override
		public void removedService(ServiceReference arg0, Object arg1) {
			serviceConf = null;
			
		}
		
	}
	
	/** method to send incoming data to admin console view */
	private void showDatainGui(){
    	Set<String> keyset =  data.keySet();
    	String showmessage = "\n";
    	for(String s : keyset){
    		showmessage += "\n" + s + ":\n" + data.get(s);
    	}
    	
    	androidContextService.transferData(showmessage);
    }
}
